/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.reactor;

import cn.hutool.core.util.IdUtil;
import com.taipingframework.boot.web.client.WebFluxHelper;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.web.properties.FluxFilterProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**
 * 四件事：
 * （1）获取请求标识
 * （2）请求标识不存在，就重新生成一个
 * （3）将请求标识传递下去
 * （4）在请求链路上下文中保存`请求标识`
 * <p>
 * 分布式日志链路追踪，这个`请求标识`非常重要。
 * 在正常业务编码中，可以从ServerHttpRequest、ServerWebExchange、Context这三个对象中获取！
 */
@Slf4j
public class WebFluxFilter implements WebFilter, Ordered {

    private FluxFilterProperty filterProperty;

    @NonNull
    @Override
    public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
        ServerHttpRequest httpRequest = exchange.getRequest();
        String requestId = httpRequest.getHeaders().getFirst(ApplicationConstant.RESPONSE_IDENTITY); // 尝试从客户端Web请求对象中获取HTTP请求的唯一标识
        if (StringUtils.isEmpty(requestId)) {
            requestId = IdUtil.objectId(); // 生成HTTP请求`唯一标识`
        }
        final String responseId = requestId;

        log.debug("全局过滤器 - 当前Web请求的唯一标识为：{}", responseId);
        ServerHttpRequest.Builder requestBuilder = httpRequest.mutate();
        requestBuilder.header(ApplicationConstant.RESPONSE_IDENTITY, responseId); // 修改HTTP请求头参数：将HTTP请求`唯一标识`写入RequestHeader
        ServerHttpRequest newHttpRequest = requestBuilder.build(); // 重新构造ServerHttpRequest对象
        ServerWebExchange newExchange = exchange.mutate().request(newHttpRequest).build(); // 更新ServerWebExchange对ServerHttpRequest的引用
        newExchange.getAttributes().putIfAbsent(ApplicationConstant.RESPONSE_IDENTITY, responseId); // 修改ServerWebExchange属性：将HTTP请求`唯一标识`写入 ServerWebExchange

        String clientIpAddress = WebFluxHelper.getIpAddr(newHttpRequest);
        newExchange.getAttributes().putIfAbsent(ApplicationConstant.REMOTE_CLIENT_IP_KEY, clientIpAddress); // 记录发送HTTP请求的远程客户端设备标识

        /*
        修改响应头的操作 更适合 在网关过滤器中 实现。
        此处被注释编码已移动位置：
        newExchange.getResponse().beforeCommit(() -> {
            // AbstractServerHttpResponse.getHeaders() 核心编码
            // this.state.get() == AbstractServerHttpResponse.State.COMMITTED ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers;
            HttpHeaders headers = newExchange.getResponse().getHeaders();

            // 在HTTP请求的响应头中记录requestId
            headers.add(ApplicationConstant.RESPONSE_IDENTITY, responseId);
            log.debug("全局过滤器 - 新增响应头`{}`值为`{}`", ApplicationConstant.RESPONSE_IDENTITY, responseId);

            // 防止重复添加相同的Key。需要特别说明的是，此处webflux过滤器响应头去重的操作执行后，
            // 后面在经过nginx转发时，如果nginx网关再次`add_header`相同参数，最终返回的HTTP响应头中该参数仍会重复出现。
            // 针对这种情况，可以灵活改动nginx网关配置来解决。
            headers.forEach((k, v) -> {
                if (null != v && v.size() > 1) {
                    List<String> list = v.stream().distinct().collect(Collectors.toList());
                    headers.replace(k, list);
                    log.debug("全局过滤器 - Web请求（{}）在结束时替换`值`重复的响应头`{}`", responseId, k);
                }
            });
            return Mono.empty().then();
        });*/

        return chain.filter(newExchange)
                .subscriberContext(context -> context.put(ServerWebExchange.class, newExchange))
                .subscriberContext(context -> context.put(ApplicationConstant.RESPONSE_IDENTITY, responseId))
                .doOnTerminate(() -> log.debug("全局过滤器 - Web请求（{}）结束", responseId));

        // return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return filterProperty == null
                ? new FluxFilterProperty().getOrderWebFluxFilter()
                : filterProperty.getOrderWebFluxFilter();
    }

    @Autowired(required = false)
    public void setFluxFilterProperty(FluxFilterProperty filterProperty) {
        this.filterProperty = filterProperty;
    }

}
